﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;

namespace System.ServiceModel.Dynamic
{


    public class Client<T> : ClientBase
    {


        private ChannelFactoryManager<T> manager;
        private T _service;        

        #region Ctor

        public Client(T service) : base()
        {
            this._service = service;
        }
     

        /// <summary>Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1" /> class using the default target endpoint from the application configuration file. </summary>
        /// <exception cref="T:System.InvalidOperationException">Either there is no default endpoint information in the configuration file, more than one endpoint in the file, or no configuration file.</exception>
        protected Client(params IEndpointBehavior[] behaviors)
            : base()
        {

            this.manager =  ManagerFactory.Get<T>
                (
                    () => new ChannelFactoryManager<T>(() => new ChannelFactory<T>("*"))
                    , "*"
                );

            this.manager.Add(behaviors);

        }

        /// <summary>Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1" /> class using the configuration information specified in the application configuration file by <paramref name="endpointConfigurationName" />. </summary>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file.</param>
        /// <exception cref="T:System.ArgumentNullException">The specified endpoint information is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        protected Client(string endpointConfigurationName, params IEndpointBehavior[] behaviors)
            : base()
        {

            if (endpointConfigurationName == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointConfigurationName");

            this.manager = ManagerFactory.Get<T>
                (
                    () => new ChannelFactoryManager<T>(() => new ChannelFactory<T>(endpointConfigurationName))
                    , endpointConfigurationName
                );

            this.manager.Add(behaviors);

        }

        /// <summary>Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1" /> class.</summary>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file.</param>
        /// <param name="remoteAddress">The address of the service.</param>
        /// <exception cref="T:System.ArgumentNullException">The endpoint is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The remote address is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        protected Client(string endpointConfigurationName, string remoteAddress, params IEndpointBehavior[] behaviors)
            : base()
        {

            if (endpointConfigurationName == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointConfigurationName");

            if (remoteAddress == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("remoteAddress");

            this.manager = ManagerFactory.Get<T>
                (
                    () => new ChannelFactoryManager<T>(() => new ChannelFactory<T>(endpointConfigurationName, new EndpointAddress(remoteAddress)))
                    , endpointConfigurationName, remoteAddress
                );

            this.manager.Add(behaviors);

        }

        /// <summary>Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1" /> class using the specified target address and endpoint information. </summary>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file.</param>
        /// <param name="remoteAddress">The address of the service.</param>
        /// <exception cref="T:System.ArgumentNullException">The endpoint is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The remote address is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        protected Client(string endpointConfigurationName, EndpointAddress remoteAddress, params IEndpointBehavior[] behaviors)
            : base()
        {

            if (endpointConfigurationName == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointConfigurationName");

            if (remoteAddress == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("remoteAddress");

            this.manager = ManagerFactory.Get<T>
                (
                    () => new ChannelFactoryManager<T>(() => new ChannelFactory<T>(endpointConfigurationName, remoteAddress))
                    , endpointConfigurationName, "remoteAddress", remoteAddress.Uri.ToString()
                );

            this.manager.Add(behaviors);

        }

        /// <summary>Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1" /> class using the specified binding and target address. </summary>
        /// <param name="binding">The binding with which to make calls to the service.</param>
        /// <param name="remoteAddress">The address of the service endpoint.</param>
        /// <exception cref="T:System.ArgumentNullException">The binding is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The remote address is null.</exception>
        protected Client(Binding binding, EndpointAddress remoteAddress, params IEndpointBehavior[] behaviors)
            : base()
        {

            if (binding == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("binding");

            if (remoteAddress == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("remoteAddress");

            this.manager = ManagerFactory.Get<T>
                (
                    () => new ChannelFactoryManager<T>(() => new ChannelFactory<T>(binding, remoteAddress))
                    , binding.Name, "remoteAddress", remoteAddress.Uri.ToString()
                );

            this.manager.Add(behaviors);

        }


        /// <summary>Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1" /> class using the specified  <see cref="T:System.ServiceModel.Description.ServiceEndpoint" />.</summary>
        /// <param name="endpoint">The endpoint for a service that allows clients to find and communicate with the service.</param>
        protected Client(ServiceEndpoint endpoint, params IEndpointBehavior[] behaviors)
            : base()
        {

            if (endpoint == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpoint");

            this.manager = ManagerFactory.Get<T>
                (
                    () => new ChannelFactoryManager<T>(() => new ChannelFactory<T>(endpoint))
                    , "endpoint", endpoint.Address.Uri.ToString(), endpoint.Binding.Name
                );

            this.manager.Add(behaviors);

        }

        /// <summary>Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1" /> class using the <paramref name="callbackInstance" /> as the callback object in a duplex conversation. </summary>
        /// <param name="callbackInstance">The callback object that the client application uses to listen for messages from the connected service.</param>
        /// <exception cref="T:System.ArgumentNullException">The callback instance is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">Either there is no default endpoint information in the configuration file, more than one endpoint in the file, or no configuration file.</exception>
        protected Client(InstanceContext callbackInstance, params IEndpointBehavior[] behaviors)
            : base()
        {

            if (callbackInstance == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("callbackInstance");

            this.manager = ManagerFactory.Get<T>
                (
                    () => new ChannelFactoryManager<T>(() => new DuplexChannelFactory<T>(callbackInstance, "*")),
                    callbackInstance.GetType().FullName, "*"
                );

            this.manager.Add(behaviors);

        }

        /// <summary>Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1" /> class using the specified callback service and endpoint configuration information. </summary>
        /// <param name="callbackInstance">The callback object that the client uses to listen for messages from the connected service.</param>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file. </param>
        /// <exception cref="T:System.ArgumentNullException">The callback instance is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The endpoint is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        protected Client(InstanceContext callbackInstance, string endpointConfigurationName, params IEndpointBehavior[] behaviors)
            : base()
        {

            if (callbackInstance == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("callbackInstance");

            if (endpointConfigurationName == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointConfigurationName");

            this.manager = ManagerFactory.Get<T>
                (
                    () => new ChannelFactoryManager<T>(() => new DuplexChannelFactory<T>(callbackInstance, endpointConfigurationName)),
                    callbackInstance.GetType().FullName, endpointConfigurationName
                );

            this.manager.Add(behaviors);
            

        }

        /// <summary>Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1" /> class.</summary>
        /// <param name="callbackInstance">The callback object that the client uses to listen for messages from the connected service.</param>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file.</param>
        /// <param name="remoteAddress">The address of the service.</param>
        /// <exception cref="T:System.ArgumentNullException">The callback instance is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The endpoint is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The remote address is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        protected Client(InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress, params IEndpointBehavior[] behaviors)
            : base()
        {

            if (callbackInstance == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("callbackInstance");

            if (endpointConfigurationName == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointConfigurationName");

            if (remoteAddress == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("remoteAddress");

            this.manager = ManagerFactory.Get<T>
                (
                    () => new ChannelFactoryManager<T>(() => new DuplexChannelFactory<T>(callbackInstance, endpointConfigurationName, new EndpointAddress(remoteAddress))),
                    callbackInstance.GetType().FullName, endpointConfigurationName, remoteAddress
                );

            this.manager.Add(behaviors);

        }


        /// <summary>Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1" /> class.</summary>
        /// <param name="callbackInstance">The callback object that the client uses to listen for messages from the connected service.</param>
        /// <param name="endpointConfigurationName">The name of the endpoint in the application configuration file.</param>
        /// <param name="remoteAddress">The address of the service.</param>
        /// <exception cref="T:System.ArgumentNullException">The callback instance is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The endpoint is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The remote address is null.</exception>
        /// <exception cref="T:System.InvalidOperationException">The endpoint cannot be found or the endpoint contract is not valid.</exception>
        protected Client(InstanceContext callbackInstance, string endpointConfigurationName, EndpointAddress remoteAddress, params IEndpointBehavior[] behaviors)
            : base()
        {

            if (callbackInstance == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("callbackInstance");

            if (endpointConfigurationName == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointConfigurationName");

            if (remoteAddress == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("remoteAddress");

            this.manager = ManagerFactory.Get<T>
                (   
                    () => new ChannelFactoryManager<T>(() => new DuplexChannelFactory<T>(callbackInstance, endpointConfigurationName, remoteAddress)),
                    callbackInstance.GetType().FullName, endpointConfigurationName, remoteAddress.Uri.ToString()
                );

            this.manager.Add(behaviors);

        }


        /// <summary>Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1" /> class. </summary>
        /// <param name="callbackInstance">The callback service.</param>
        /// <param name="binding">The binding with which to call the service.</param>
        /// <param name="remoteAddress">The address of the service endpoint.</param>
        /// <exception cref="T:System.ArgumentNullException">The callback instance is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The binding is null.</exception>
        /// <exception cref="T:System.ArgumentNullException">The remote address is null.</exception>
        protected Client(InstanceContext callbackInstance, Binding binding, EndpointAddress remoteAddress, params IEndpointBehavior[] behaviors)
            : base()
        {
            if (callbackInstance == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("callbackInstance");

            if (binding == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("binding");

            if (remoteAddress == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("remoteAddress");

            this.manager = ManagerFactory.Get<T>
                (
                    () => new ChannelFactoryManager<T>(() => new DuplexChannelFactory<T>(callbackInstance, binding, remoteAddress)),
                    callbackInstance.GetType().FullName, binding.Name, remoteAddress.Uri.ToString()
                );

            this.manager.Add(behaviors);

        }


        /// <summary>Initializes a new instance of the <see cref="T:System.ServiceModel.Client`1" /> class using the specified  <see cref="T:System.ServiceModel.InstanceContext" /> and  <see cref="T:System.ServiceModel.Description.ServiceEndpoint" /> objects.</summary>
        /// <param name="callbackInstance">The callback object that the client application uses to listen for messages from the connected service.</param>
        /// <param name="endpoint">The endpoint for a service that allows clients to find and communicate with the service.</param>
        protected Client(InstanceContext callbackInstance, ServiceEndpoint endpoint, params IEndpointBehavior[] behaviors)
            : base()
        {

            if (callbackInstance == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("callbackInstance");

            if (endpoint == null)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpoint");

            this.manager = ManagerFactory.Get<T>
                (
                    () => new ChannelFactoryManager<T>(() => new DuplexChannelFactory<T>(callbackInstance, endpoint)),
                    callbackInstance.GetType().FullName, endpoint.Address.Uri.ToString(), endpoint.Binding.Name
                );

            this.manager.Add(behaviors);

        }


        #endregion

        
        #region Invoker

        public T CreateInvoker()
        {

            if (_service != null)
                return _service;

            T r = this.manager.CreateChannel();

            ((IClientChannel)r).Faulted += Client_Faulted;

            return r;

        }
       

        #endregion


        public void AddBehaviors(params IEndpointBehavior[] behaviors)
        {
            this.manager.Add(behaviors);
        }


    }

}
